Ensure single trailing newline in generated files - #6781
Conversation
|
Integration test failure in was https://github.com/phoenixframework/phoenix/actions/runs/30761860392/job/91533719530?pr=6781 |
| <%= if (fields = Mix.Phoenix.Schema.format_fields_for_schema(schema)) != "" do %><%= fields %> | ||
| <% end %><%= for {_, k, _, _} <- schema.assocs do %> field <%= inspect k %>, <%= if schema.binary_id do %>:binary_id<% else %>:id<% end %> |
There was a problem hiding this comment.
Maybe we should revisit #6015, then it wouldn't matter as much how the templates do whitespace
There was a problem hiding this comment.
I see how that is related, but it wouldn't solve all cases that we cover here in this PR. Among other things mix format would not touch:
AGENTS.md, or any other markdown content*.potGettext filesapp.js.gitignoredefault.css
On the other hand, mix format is still complementary for the "final touch", and would handle things we don't handle here, for example reformatting lines that may become too long depending on a module name.
This one particular code hunk is perhaps the trickiest change because it handles the case where there are no fields in the schema. Without the if we'd unconditionally render a newline, such that:
-
Schema generated without attributes (
mix phx.gen.schema Blog.Post posts):schema "posts" do timestamps(type: :naive_datetime) end
-
Schema generated with only reference attributes (
mix phx.gen.schema Blog.Post posts user_id:references:users):schema "posts" do field :user_id, :id timestamps() end
Both cases would be handled by a pass of mix format.
There was a problem hiding this comment.
Considering the precedent of format_fields_for_schema/1, we can simplify this particular template with another helper format_schema_body/2 that takes care of joining the multiple components without this line break dance in the template.
I can volunteer to revisit #6015 as a follow up, essentially applying Code.format_string!/2 to generated Elixir code, which would handle the remaining cases like long lines.
Replicate the approach from phoenixframework/phoenix#6781, since clearly the Phoenix installer and `mix new` share the `assert_file/1` and `assert_file/2` helpers. All generated files are candidates for these two rules: 1. Every file must end with a single trailing newline. 2. No file may contain consecutive blank lines. Instead of updating lots of tests (which would cause a lot of churn and be a future maintenance burden, easy to miss in new tests), update the helpers to enforce these rules on every file. Exceptions can be easily added in the future if needed.
1991fcf to
b679931
Compare
|
Rebased onto latest main to pick up flaky integration test fix from #6783. |
| // If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file. | ||
| // To load it, simply add a second `<link>` to your `root.html.heex` file. | ||
| <%= if @html do %> | ||
| // To load it, simply add a second `<link>` to your `root.html.heex` file.<%= if @html do %> |
There was a problem hiding this comment.
| // To load it, simply add a second `<link>` to your `root.html.heex` file.<%= if @html do %> | |
| // To load it, simply add a second `<link>` to your `root.html.heex` file.<%= if @html do %> | |
I think this one is deliberate
There was a problem hiding this comment.
Thanks, good catch. The intention was to remove additional newlines at the end of file when html: false. The correct change that preserves the blank line separating the comment blocks is:
| // To load it, simply add a second `<link>` to your `root.html.heex` file.<%= if @html do %> | |
| // To load it, simply add a second `<link>` to your `root.html.heex` file.<%= if @html do %> | |
I.e., the if moves up, but the blank line remains. Verified across all four permutations of :html and :live.
| ## Channels<%= if existing_channel do %> | ||
|
|
||
| channel "<%= existing_channel[:singular] %>:*", <%= existing_channel[:module] %>Channel | ||
| <% else %> | ||
| channel "<%= existing_channel[:singular] %>:*", <%= existing_channel[:module] %>Channel<% else %> |
There was a problem hiding this comment.
This would render as
## Channels
channel ...
There was a problem hiding this comment.
Yes. This is one weird case.
The generated code is an Elixir module-level comment before the channel macro call.
- It seems non-standard that it has a double hash-sign.
- We don't do such sectional comments in Endpoint sockets or plugs.
I'm pro removing the comment altogether. Will do in a separate commit so we can evaluate and decide whether to keep or drop.
|
I'm doing an additional and more thorough review of each permutation of the template changes to avoid introducing unintended behavior/whitespace changes. |
b679931 to
6a27152
Compare
And no unintentional consecutive blank lines in the generated content. Those two issues are rather aesthetic, but downstream users notice and they are a common source of code churn and maintenance overheard that we can prevent from now on. The single trailing newline is a common convention in Unix and POSIX systems, and it is also what the Elixir formatter does (`Code.format_file!` always appends a trailing newline [1]). The consecutive blank lines in Elixir code are also automatically removed by the Elixir formatter. It is not a strict rule for other files, but generally they are not expected and most of the time added unintentionally, e.g. when using conditional EEx templates or concatenating strings (AGENTS.md / usage rules). Except for a handful of generated files (favicon.ico, phoenix.png and *.pem certificates), all generated files are candidates for those two rules. Instead of updating lots of tests (which would cause a lot of churn and be a future maintenance burden, easy to miss in new tests), we update `MixHelper.assert_file/1` to check for those two rules in all current and future generated files. If we need more exceptions in the future, it is easy to change `assert_file/1` to add them. [1]: https://github.com/elixir-lang/elixir/blob/545dddf138e4cb1ee874e6f2c26882c9b438f551/lib/elixir/lib/code.ex#L1137-L1141
6a27152 to
f213110
Compare
Instead of fighting with whitespace in the template, follow the precedent of `format_fields_for_schema/1` and create a helper that formats the entire schema body, including fields, associations, scope, and timestamps. All permutations of the presence of fields, associations, and scope are tested to ensure correct formatting.
We don't do this in other files, and the double hash comment is also non-standard.
rhcarvalho
left a comment
There was a problem hiding this comment.
Detailed human review of each change considering every permutation of the templates before and after, showing the exact whitespace fixes (in per file comments). Always one of:
- Missing newline at EOF
- Extra blank line at EOF
- Two or more consecutive blank lines in the middle of a generated file
The changes to the assert_file test helper guarantee we won't reintroduce those classes of whitespace issues in the future, hopefully reducing future churn.
There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: html: true, live: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -80,4 +80,3 @@
window.liveReloader = reloader
})
}
-Full Rendered Outputs (Before & After)
Before Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import {hooks as colocatedHooks} from "phoenix-colocated/my_app_web"
import topbar from "../vendor/topbar"
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
hooks: {...colocatedHooks},
})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page
liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
// The lines below enable quality of life phoenix_live_reload
// development features:
//
// 1. stream server logs to the browser console
// 2. click on elements to jump to their definitions in your code editor
//
if (process.env.NODE_ENV === "development") {
window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => {
// Enable server log streaming to client.
// Disable with reloader.disableServerLogs()
reloader.enableServerLogs()
// Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
//
// * click with "c" key pressed to open at caller location
// * click with "d" key pressed to open at function component definition location
let keyDown
window.addEventListener("keydown", e => keyDown = e.key)
window.addEventListener("keyup", _e => keyDown = null)
window.addEventListener("click", e => {
if(keyDown === "c"){
e.preventDefault()
e.stopImmediatePropagation()
reloader.openEditorAtCaller(e.target)
} else if(keyDown === "d"){
e.preventDefault()
e.stopImmediatePropagation()
reloader.openEditorAtDef(e.target)
}
}, true)
window.liveReloader = reloader
})
}After Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix"
import {LiveSocket} from "phoenix_live_view"
import {hooks as colocatedHooks} from "phoenix-colocated/my_app_web"
import topbar from "../vendor/topbar"
const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
const liveSocket = new LiveSocket("/live", Socket, {
longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken},
hooks: {...colocatedHooks},
})
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page
liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
// The lines below enable quality of life phoenix_live_reload
// development features:
//
// 1. stream server logs to the browser console
// 2. click on elements to jump to their definitions in your code editor
//
if (process.env.NODE_ENV === "development") {
window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => {
// Enable server log streaming to client.
// Disable with reloader.disableServerLogs()
reloader.enableServerLogs()
// Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
//
// * click with "c" key pressed to open at caller location
// * click with "d" key pressed to open at function component definition location
let keyDown
window.addEventListener("keydown", e => keyDown = e.key)
window.addEventListener("keyup", _e => keyDown = null)
window.addEventListener("click", e => {
if(keyDown === "c"){
e.preventDefault()
e.stopImmediatePropagation()
reloader.openEditorAtCaller(e.target)
} else if(keyDown === "d"){
e.preventDefault()
e.stopImmediatePropagation()
reloader.openEditorAtDef(e.target)
}
}, true)
window.liveReloader = reloader
})
}Permutation: html: true, live: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -81,10 +81,9 @@
// })
// }
-
// Handle flash close
document.querySelectorAll("[role=alert][data-flash]").forEach((el) => {
el.addEventListener("click", () => {
el.setAttribute("hidden", "")
})
-})
\ No newline at end of file
+})Full Rendered Outputs (Before & After)
Before Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
// import {Socket} from "phoenix"
// import {LiveSocket} from "phoenix_live_view"
// import {hooks as colocatedHooks} from "phoenix-colocated/my_app_web"
// import topbar from "../vendor/topbar"
// const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
// const liveSocket = new LiveSocket("/live", Socket, {
// longPollFallbackMs: 2500,
// params: {_csrf_token: csrfToken},
// hooks: {...colocatedHooks},
// })
// Show progress bar on live navigation and form submits
// topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
// window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
// window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page
// liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
// window.liveSocket = liveSocket
// The lines below enable quality of life phoenix_live_reload
// development features:
//
// 1. stream server logs to the browser console
// 2. click on elements to jump to their definitions in your code editor
//
// if (process.env.NODE_ENV === "development") {
// window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => {
// // Enable server log streaming to client.
// // Disable with reloader.disableServerLogs()
// reloader.enableServerLogs()
//
// // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
// //
// // * click with "c" key pressed to open at caller location
// // * click with "d" key pressed to open at function component definition location
// let keyDown
// window.addEventListener("keydown", e => keyDown = e.key)
// window.addEventListener("keyup", _e => keyDown = null)
// window.addEventListener("click", e => {
// if(keyDown === "c"){
// e.preventDefault()
// e.stopImmediatePropagation()
// reloader.openEditorAtCaller(e.target)
// } else if(keyDown === "d"){
// e.preventDefault()
// e.stopImmediatePropagation()
// reloader.openEditorAtDef(e.target)
// }
// }, true)
//
// window.liveReloader = reloader
// })
// }
// Handle flash close
document.querySelectorAll("[role=alert][data-flash]").forEach((el) => {
el.addEventListener("click", () => {
el.setAttribute("hidden", "")
})
})```
##### After Commit
```javascript
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
// import {Socket} from "phoenix"
// import {LiveSocket} from "phoenix_live_view"
// import {hooks as colocatedHooks} from "phoenix-colocated/my_app_web"
// import topbar from "../vendor/topbar"
// const csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
// const liveSocket = new LiveSocket("/live", Socket, {
// longPollFallbackMs: 2500,
// params: {_csrf_token: csrfToken},
// hooks: {...colocatedHooks},
// })
// Show progress bar on live navigation and form submits
// topbar.config({barColors: {0: "#29d"}, shadowColor: "rgba(0, 0, 0, .3)"})
// window.addEventListener("phx:page-loading-start", _info => topbar.show(300))
// window.addEventListener("phx:page-loading-stop", _info => topbar.hide())
// connect if there are any LiveViews on the page
// liveSocket.connect()
// expose liveSocket on window for web console debug logs and latency simulation:
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
// window.liveSocket = liveSocket
// The lines below enable quality of life phoenix_live_reload
// development features:
//
// 1. stream server logs to the browser console
// 2. click on elements to jump to their definitions in your code editor
//
// if (process.env.NODE_ENV === "development") {
// window.addEventListener("phx:live_reload:attached", ({detail: reloader}) => {
// // Enable server log streaming to client.
// // Disable with reloader.disableServerLogs()
// reloader.enableServerLogs()
//
// // Open configured PLUG_EDITOR at file:line of the clicked element's HEEx component
// //
// // * click with "c" key pressed to open at caller location
// // * click with "d" key pressed to open at function component definition location
// let keyDown
// window.addEventListener("keydown", e => keyDown = e.key)
// window.addEventListener("keyup", _e => keyDown = null)
// window.addEventListener("click", e => {
// if(keyDown === "c"){
// e.preventDefault()
// e.stopImmediatePropagation()
// reloader.openEditorAtCaller(e.target)
// } else if(keyDown === "d"){
// e.preventDefault()
// e.stopImmediatePropagation()
// reloader.openEditorAtDef(e.target)
// }
// }, true)
//
// window.liveReloader = reloader
// })
// }
// Handle flash close
document.querySelectorAll("[role=alert][data-flash]").forEach((el) => {
el.addEventListener("click", () => {
el.setAttribute("hidden", "")
})
})Permutation: html: false, live: true (Output unchanged)
Unified Output Diff
(No output changes)Full Rendered Outputs (Before & After)
Before Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.After Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.Permutation: html: false, live: false (Output unchanged)
Unified Output Diff
(No output changes)Full Rendered Outputs (Before & After)
Before Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.After Commit
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "../vendor/some-package.js"
//
// Alternatively, you can `npm install some-package --prefix assets` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// If you have dependencies that try to import CSS, esbuild will generate a separate `app.css` file.
// To load it, simply add a second `<link>` to your `root.html.heex` file.There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: ecto: true (Output unchanged)
Unified Output Diff
(No output changes)Full Rendered Outputs (Before & After)
Before Commit
## This is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""After Commit
## This is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.
## From Ecto.Changeset.cast/4
msgid "can't be blank"
msgstr ""
## From Ecto.Changeset.unique_constraint/3
msgid "has already been taken"
msgstr ""
## From Ecto.Changeset.put_change/3
msgid "is invalid"
msgstr ""
## From Ecto.Changeset.validate_acceptance/3
msgid "must be accepted"
msgstr ""
## From Ecto.Changeset.validate_format/3
msgid "has invalid format"
msgstr ""
## From Ecto.Changeset.validate_subset/3
msgid "has an invalid entry"
msgstr ""
## From Ecto.Changeset.validate_exclusion/3
msgid "is reserved"
msgstr ""
## From Ecto.Changeset.validate_confirmation/3
msgid "does not match confirmation"
msgstr ""
## From Ecto.Changeset.no_assoc_constraint/3
msgid "is still associated with this entry"
msgstr ""
msgid "are still associated with this entry"
msgstr ""
## From Ecto.Changeset.validate_length/3
msgid "should have %{count} item(s)"
msgid_plural "should have %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be %{count} character(s)"
msgid_plural "should be %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be %{count} byte(s)"
msgid_plural "should be %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at least %{count} item(s)"
msgid_plural "should have at least %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} character(s)"
msgid_plural "should be at least %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at least %{count} byte(s)"
msgid_plural "should be at least %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should have at most %{count} item(s)"
msgid_plural "should have at most %{count} item(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} character(s)"
msgid_plural "should be at most %{count} character(s)"
msgstr[0] ""
msgstr[1] ""
msgid "should be at most %{count} byte(s)"
msgid_plural "should be at most %{count} byte(s)"
msgstr[0] ""
msgstr[1] ""
## From Ecto.Changeset.validate_number/3
msgid "must be less than %{number}"
msgstr ""
msgid "must be greater than %{number}"
msgstr ""
msgid "must be less than or equal to %{number}"
msgstr ""
msgid "must be greater than or equal to %{number}"
msgstr ""
msgid "must be equal to %{number}"
msgstr ""Permutation: ecto: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -7,4 +7,3 @@
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.
-Full Rendered Outputs (Before & After)
Before Commit
## This is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.
After Commit
## This is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here has no
## effect: edit them in PO (`.po`) files instead.There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: (javascript or css): true, sqlite3: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -38,4 +38,3 @@
# Database files
*.db
*.db-*
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
# Database files
*.db
*.db-*
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
# Database files
*.db
*.db-*
Permutation: (javascript or css): true, sqlite3: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -34,4 +34,3 @@
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
Permutation: (javascript or css): false, sqlite3: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -28,4 +28,3 @@
# Database files
*.db
*.db-*
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
# Database files
*.db
*.db-*
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
# Database files
*.db
*.db-*
Permutation: (javascript or css): false, sqlite3: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -24,4 +24,3 @@
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app-*.tar
There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: (javascript or css): true, sqlite3: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -38,4 +38,3 @@
# Database files
*.db
*.db-*
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
# Database files
*.db
*.db-*
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
# Database files
*.db
*.db-*
Permutation: (javascript or css): true, sqlite3: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -34,4 +34,3 @@
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
# Ignore assets that are produced by build tools.
/priv/static/assets/
# Ignore digested assets cache.
/priv/static/cache_manifest.json
# In case you use Node.js/npm, you want to ignore these.
npm-debug.log
/assets/node_modules/
Permutation: (javascript or css): false, sqlite3: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -28,4 +28,3 @@
# Database files
*.db
*.db-*
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
# Database files
*.db
*.db-*
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
# Database files
*.db
*.db-*
Permutation: (javascript or css): false, sqlite3: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -24,4 +24,3 @@
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
-Full Rendered Outputs (Before & After)
Before Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
After Commit
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where 3rd-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Temporary files, for example, from tests.
/tmp/
# Ignore package tarball (built via "mix hex.build").
my_app_web-*.tar
There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: mailer: true, html: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -16,7 +16,7 @@
# Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view,
enable_expensive_runtime_checks: true
-
+
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
- sort_verified_routes_query_params: true
\ No newline at end of file
+ sort_verified_routes_query_params: trueFull Rendered Outputs (Before & After)
Before Commit
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# In test we don't send emails
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Test
# Disable swoosh api client as it is only required for production adapters
config :swoosh, :api_client, false
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view,
enable_expensive_runtime_checks: true
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: true```
##### After Commit
```elixir
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# In test we don't send emails
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Test
# Disable swoosh api client as it is only required for production adapters
config :swoosh, :api_client, false
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view,
enable_expensive_runtime_checks: true
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: truePermutation: mailer: true, html: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -12,7 +12,7 @@
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
-
+
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
- sort_verified_routes_query_params: true
\ No newline at end of file
+ sort_verified_routes_query_params: trueFull Rendered Outputs (Before & After)
Before Commit
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# In test we don't send emails
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Test
# Disable swoosh api client as it is only required for production adapters
config :swoosh, :api_client, false
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: true```
##### After Commit
```elixir
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# In test we don't send emails
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.Test
# Disable swoosh api client as it is only required for production adapters
config :swoosh, :api_client, false
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: truePermutation: mailer: false, html: true (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -9,7 +9,7 @@
# Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view,
enable_expensive_runtime_checks: true
-
+
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
- sort_verified_routes_query_params: true
\ No newline at end of file
+ sort_verified_routes_query_params: trueFull Rendered Outputs (Before & After)
Before Commit
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view,
enable_expensive_runtime_checks: true
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: true```
##### After Commit
```elixir
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Enable helpful, but potentially expensive runtime checks
config :phoenix_live_view,
enable_expensive_runtime_checks: true
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: truePermutation: mailer: false, html: false (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -5,7 +5,7 @@
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
-
+
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
- sort_verified_routes_query_params: true
\ No newline at end of file
+ sort_verified_routes_query_params: trueFull Rendered Outputs (Before & After)
Before Commit
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: true```
##### After Commit
```elixir
import Config
# Print only warnings and errors during test
config :logger, level: :warning
# Initialize plugs at runtime for faster test compilation
config :phoenix, :plug_init_mode, :runtime
# Sort query params output of verified routes for robust url comparisons
config :phoenix,
sort_verified_routes_query_params: true| <%= if (fields = Mix.Phoenix.Schema.format_fields_for_schema(schema)) != "" do %><%= fields %> | ||
| <% end %><%= for {_, k, _, _} <- schema.assocs do %> field <%= inspect k %>, <%= if schema.binary_id do %>:binary_id<% else %>:id<% end %> |
There was a problem hiding this comment.
Considering the precedent of format_fields_for_schema/1, we can simplify this particular template with another helper format_schema_body/2 that takes care of joining the multiple components without this line break dance in the template.
I can volunteer to revisit #6015 as a follow up, essentially applying Code.format_string!/2 to generated Elixir code, which would handle the remaining cases like long lines.
There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: With fields (title:string), no assocs (Output unchanged)
Unified Output Diff
(No output changes)Full Rendered Outputs (Before & After)
Before Commit
defmodule Phoenix.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
timestamps()
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [:title])
|> validate_required([:title])
end
endAfter Commit
defmodule Phoenix.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
field :title, :string
timestamps()
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [:title])
|> validate_required([:title])
end
endPermutation: No fields, no assocs (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -3,8 +3,6 @@
import Ecto.Changeset
schema "posts" do
-
-
timestamps()
end
Full Rendered Outputs (Before & After)
Before Commit
defmodule Phoenix.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
timestamps()
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [])
|> validate_required([])
end
endAfter Commit
defmodule Phoenix.Blog.Post do
use Ecto.Schema
import Ecto.Changeset
schema "posts" do
timestamps()
end
@doc false
def changeset(post, attrs) do
post
|> cast(attrs, [])
|> validate_required([])
end
endPermutation: No fields, with assoc (post_id:references) (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -3,7 +3,6 @@
import Ecto.Changeset
schema "comments" do
-
field :post_id, :id
timestamps()Full Rendered Outputs (Before & After)
Before Commit
defmodule Phoenix.Blog.Comment do
use Ecto.Schema
import Ecto.Changeset
schema "comments" do
field :post_id, :id
timestamps()
end
@doc false
def changeset(comment, attrs) do
comment
|> cast(attrs, [])
|> validate_required([])
end
endAfter Commit
defmodule Phoenix.Blog.Comment do
use Ecto.Schema
import Ecto.Changeset
schema "comments" do
field :post_id, :id
timestamps()
end
@doc false
def changeset(comment, attrs) do
comment
|> cast(attrs, [])
|> validate_required([])
end
endPermutation: With fields (title:string) and assoc (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -3,7 +3,6 @@
import Ecto.Changeset
schema "comments" do
-
field :post_id, :id
timestamps()Full Rendered Outputs (Before & After)
Before Commit
defmodule Phoenix.Blog.Comment do
use Ecto.Schema
import Ecto.Changeset
schema "comments" do
field :post_id, :id
timestamps()
end
@doc false
def changeset(comment, attrs) do
comment
|> cast(attrs, [])
|> validate_required([])
end
endAfter Commit
defmodule Phoenix.Blog.Comment do
use Ecto.Schema
import Ecto.Changeset
schema "comments" do
field :post_id, :id
timestamps()
end
@doc false
def changeset(comment, attrs) do
comment
|> cast(attrs, [])
|> validate_required([])
end
end| ## Channels<%= if existing_channel do %> | ||
|
|
||
| channel "<%= existing_channel[:singular] %>:*", <%= existing_channel[:module] %>Channel | ||
| <% else %> | ||
| channel "<%= existing_channel[:singular] %>:*", <%= existing_channel[:module] %>Channel<% else %> |
There was a problem hiding this comment.
Yes. This is one weird case.
The generated code is an Elixir module-level comment before the channel macro call.
- It seems non-standard that it has a double hash-sign.
- We don't do such sectional comments in Endpoint sockets or plugs.
I'm pro removing the comment altogether. Will do in a separate commit so we can evaluate and decide whether to keep or drop.
There was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: existing_channel present (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -6,8 +6,6 @@
# It's possible to control the websocket connection and
# assign values that can be accessed by your channel topics.
- ## Channels
-
channel "user:*", UserChannel
# Socket params are passed from the client and canFull Rendered Outputs (Before & After)
Before Commit
defmodule UserSocket do
use Phoenix.Socket
# A Socket handler
#
# It's possible to control the websocket connection and
# assign values that can be accessed by your channel topics.
## Channels
channel "user:*", UserChannel
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error` or `{:error, term}`. To control the
# response the client receives in that case, [define an error handler in the
# websocket
# configuration](https://phoenix.hexdocs.pm/Phoenix.Endpoint.html#socket/3-websocket-configuration).
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
# Socket IDs are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# UserWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
@impl true
def id(_socket), do: nil
endAfter Commit
defmodule UserSocket do
use Phoenix.Socket
# A Socket handler
#
# It's possible to control the websocket connection and
# assign values that can be accessed by your channel topics.
channel "user:*", UserChannel
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error` or `{:error, term}`. To control the
# response the client receives in that case, [define an error handler in the
# websocket
# configuration](https://phoenix.hexdocs.pm/Phoenix.Endpoint.html#socket/3-websocket-configuration).
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
# Socket IDs are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# UserWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
@impl true
def id(_socket), do: nil
endPermutation: existing_channel nil (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -6,7 +6,6 @@
# It's possible to control the websocket connection and
# assign values that can be accessed by your channel topics.
- ## Channels
# Uncomment the following line to define a "room:*" topic
# pointing to the `UserWeb.RoomChannel`:
#
@@ -19,7 +18,6 @@
# See the [`Channels guide`](https://phoenix.hexdocs.pm/channels.html)
# for further details.
-
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns intoFull Rendered Outputs (Before & After)
Before Commit
defmodule UserSocket do
use Phoenix.Socket
# A Socket handler
#
# It's possible to control the websocket connection and
# assign values that can be accessed by your channel topics.
## Channels
# Uncomment the following line to define a "room:*" topic
# pointing to the `UserWeb.RoomChannel`:
#
# channel "room:*", UserWeb.RoomChannel
#
# To create a channel file, use the mix task:
#
# mix phx.gen.channel Room
#
# See the [`Channels guide`](https://phoenix.hexdocs.pm/channels.html)
# for further details.
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error` or `{:error, term}`. To control the
# response the client receives in that case, [define an error handler in the
# websocket
# configuration](https://phoenix.hexdocs.pm/Phoenix.Endpoint.html#socket/3-websocket-configuration).
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
# Socket IDs are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# UserWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
@impl true
def id(_socket), do: nil
endAfter Commit
defmodule UserSocket do
use Phoenix.Socket
# A Socket handler
#
# It's possible to control the websocket connection and
# assign values that can be accessed by your channel topics.
# Uncomment the following line to define a "room:*" topic
# pointing to the `UserWeb.RoomChannel`:
#
# channel "room:*", UserWeb.RoomChannel
#
# To create a channel file, use the mix task:
#
# mix phx.gen.channel Room
#
# See the [`Channels guide`](https://phoenix.hexdocs.pm/channels.html)
# for further details.
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error` or `{:error, term}`. To control the
# response the client receives in that case, [define an error handler in the
# websocket
# configuration](https://phoenix.hexdocs.pm/Phoenix.Endpoint.html#socket/3-websocket-configuration).
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
@impl true
def connect(_params, socket, _connect_info) do
{:ok, socket}
end
# Socket IDs are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# UserWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
@impl true
def id(_socket), do: nil
endThere was a problem hiding this comment.
Computed Outputs Across Conditional Permutations
Permutation: Full-stack project (Representative of all 16 flag permutations) (Output changed by commit)
Unified Output Diff
--- Before
+++ After
@@ -43,7 +43,6 @@
- Ensure **clean typography, spacing, and layout balance** for a refined, premium look
- Focus on **delightful details** like hover effects, loading states, and smooth page transitions
-
<!-- usage-rules-start -->
<!-- phoenix:elixir-start -->
@@ -446,4 +445,4 @@
- **Never** use `<.form let={f} ...>` in the template, instead **always use `<.form for={@form} ...>`**, then drive all form references from the form assign as in `@form[:field]`. The UI should **always** be driven by a `to_form/2` assigned in the LiveView module that is derived from a changeset
<!-- phoenix:liveview-end -->
-<!-- usage-rules-end -->
\ No newline at end of file
+<!-- usage-rules-end -->Full Rendered Outputs (Before & After) omitted for brevity.
|
Forgot to post on the previous comment. The diff generation for manual review was generated with an AI-assisted one-off script, included here for reference/transpareny.
|
|
🙌🏻 |
And no unintentional consecutive blank lines in the generated content.
Those two issues are rather aesthetic, but downstream users notice and they are a common source of code churn and maintenance overheard that we can prevent from now on.
The single trailing newline is a common convention in Unix and POSIX systems, and it is also what the Elixir formatter does (
Code.format_file!always appends a trailing newline 1).The consecutive blank lines in Elixir code are also automatically removed by the Elixir formatter. It is not a strict rule for other files, but generally they are not expected and most of the time added unintentionally, e.g. when using conditional EEx templates or concatenating strings (AGENTS.md / usage rules).
Except for a handful of generated files (favicon.ico, phoenix.png and *.pem certificates), all generated files are candidates for those two rules. Instead of updating lots of tests (which would cause a lot of churn and be a future maintenance burden, easy to miss in new tests), we update
MixHelper.assert_file/1to check for those two rules in all current and future generated files. If we need more exceptions in the future, it is easy to changeassert_file/1to add them.